package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.product.domain.ProductSku;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.query.SkuQuery;
import com.spzx.product.service.IProductSkuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.vo.ProductSkuVo;
import com.spzx.product.vo.SkuPriceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.swing.text.StyledEditorKit;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 商品sku 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2024-11-12
 */
@Service
@Slf4j
public class ProductSkuServiceImpl extends ServiceImpl<ProductSkuMapper, ProductSku> implements IProductSkuService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<ProductSkuVo> getTopSale() {
        return baseMapper.getTopSale();
    }

    @Override
    public List<ProductSkuVo> selectProductSkuList(SkuQuery skuQuery) {

        return baseMapper.selectProductSkuList(skuQuery);
    }

    @Override
    public SkuPriceVo getSkuPrice(Long skuId) {

        LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductSku::getId, skuId);
        queryWrapper.select(ProductSku::getSalePrice, ProductSku::getMarketPrice);
        ProductSku productSku = baseMapper.selectOne(queryWrapper);

        SkuPriceVo skuPriceVo = new SkuPriceVo();
        skuPriceVo.setSalePrice(productSku.getSalePrice());
        skuPriceVo.setMarketPrice(productSku.getMarketPrice());
        skuPriceVo.setSkuId(skuId);

        return skuPriceVo;

    }

    @Override
    public Map<String, Long> getSkuSpecValue(Long id) {

        // 查询一个商品的所有sku
        LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(ProductSku::getProductId, id)
                .select(ProductSku::getId, ProductSku::getSkuSpec);
        List<ProductSku> productSkuList = baseMapper.selectList(queryWrapper);

        HashMap<String, Long> map = new HashMap<>();
        productSkuList.forEach(productSku -> {
            map.put(productSku.getSkuSpec(), productSku.getId());
        });
        return map;
    }

    @Override
    public ProductSku getProductSku(Long skuId) {

        log.info(Thread.currentThread().getName() + "获取商品SKU");

        try {
            //先从缓存中获取数据
            String dataKey = "product:sku:" + skuId;
            ProductSku productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);
            //命中缓存
            if(productSku != null){
                log.info(Thread.currentThread().getName() + "命中缓存");
                return productSku;
            }

            //没有命中缓存，则查询数据库。为了避免缓存击穿，因此查询数据库的时候要添加分布式锁，保证同时只有一个进程被调用
            //定义分布式锁的key
            String lockKey = "product:sku:lock:" + skuId;
            //定义uuid值，用于释放锁
            String lockValue = UUID.randomUUID().toString().replaceAll("-", ""); //+ Thread.currentThread().getName();
            //获取锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
            if(flag){//获取锁成功

                try {
                    log.info(Thread.currentThread().getName() + "获取锁成功");


                    //再次检查缓存，是否有数据
                    productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);
                    if(productSku != null){
                        return productSku;
                    }

                    //从数据库中查询数据
                    //this.getById(skuId);
                    productSku = baseMapper.selectById(skuId);

                    //将商品sku数据存入redis
                    long ttl = productSku == null ? 60 : 10 * 60; //10分钟
                    if(productSku == null){
                        //防止将null存入缓存后，查询结果和没有命中缓存无法区分
                        productSku = new ProductSku();
                    }
                    redisTemplate.opsForValue().set(dataKey, productSku, ttl, TimeUnit.SECONDS);

                    return productSku;
                } catch (Exception e) {
                    log.info(Thread.currentThread().getName() + "执行业务发生异常");
                    throw new RuntimeException(e);
                } finally {
                    log.info(Thread.currentThread().getName() + "释放锁");
                    //使用lua确保所释放的原子性
                    String script = """
                        if redis.call('get', KEYS[1]) == ARGV[1] then
                            return redis.call('del', KEYS[1])
                        else
                            return 0
                        end
                        """;
                    /*Boolean lock = (Boolean)redisTemplate.execute(
                            new DefaultRedisScript<>(script, Boolean.class),
                            Arrays.asList(lockKey),
                            lockValue);*/
                    DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(script);
                    redisScript.setResultType(Boolean.class);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockValue);
                }

            }else{//获取锁失败

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info(Thread.currentThread().getName() + "获取锁失败");
                return this.getProductSku(skuId);
            }
        } catch (RuntimeException e) {
            log.error(Thread.currentThread().getName() + "获取商品SKU异常 {}", e);
            return baseMapper.selectById(skuId);
        }
    }
}
